﻿package visualizer.code {
	
	// Imported Classes
	import flash.display.MovieClip;
    import flash.display.Graphics;
    import flash.events.Event;
	
	public class Visualizer extends MovieClip {
				
		// Constants
		const FRAME_RATE = 25;
		const BEAT_THRESHOLD = 1.15;
		const ALL_BANDS:uint = 0; // [0-9]
		const ALL_BANDS_MIN:uint = 0;
		const ALL_BANDS_MAX:uint = 9;
		const LOW_BAND:uint = 1;  // [0-2]
		const LOW_BAND_MIN:uint = 0;
		const LOW_BAND_MAX:uint = 2;
		const MID_BAND:uint = 2;  // [3-6]
		const MID_BAND_MIN:uint = 3;
		const MID_BAND_MAX:uint = 6;
		const HIGH_BAND:uint = 3; // [7-9]
		const HIGH_BAND_MIN:uint = 7;
		const HIGH_BAND_MAX:uint = 9;
		
		// Current Amplitude data
		protected var amplitudes:Vector.<Number>;
		
		// Amplitude History (used to detect changes in the music's character)
		var ampIndex:uint;
		var ampHistory:Vector.<Vector.<Number>>;
		
		// Beat information
		var lowBeat:Boolean;
		var midBeat:Boolean;
		var highBeat:Boolean;
		
		public function Visualizer()
		{
			// Initialize Volume Amplitudes
			amplitudes = new Vector.<Number>(10,true);
			
			// Initialize Amplitude History Components
			ampIndex = 0;
			ampHistory = new Vector.<Vector.<Number>>(FRAME_RATE,true);
			var i:uint;
			for(i=0;i<FRAME_RATE;i++) ampHistory[i] = amplitudes;
			
			// Initialize Beat Tracking
			lowBeat = midBeat = highBeat = false;
			
			// Add Event Listeners
			addEventListener(Event.ENTER_FRAME,onEnterFrame);			
		}
		
		protected function drawFrame()
		{
			// Constants
			var CHANNEL_LENGTH:uint = 10;
			var PLOT_HEIGHT:int = 200;
			
			// Access the MovieClip's graphics object
            var g:Graphics = this.graphics;
            
			// Clear Previous Plots
            g.clear();
       
	   		// Draw Left Channel
            g.lineStyle(0, 0x6600CC);
            g.beginFill(0x6600CC);
            g.moveTo(0, PLOT_HEIGHT);
            
            var n:Number = 0;
            for (var i:int = 0; i < CHANNEL_LENGTH; i++) {
                n = (amplitudes[i] * PLOT_HEIGHT);
                g.lineTo(i * 55+55, PLOT_HEIGHT - n);
            }

            g.lineTo(CHANNEL_LENGTH * 2, PLOT_HEIGHT);
            g.endFill();
		}
		
		protected function recentVolAvg(frames:uint = 5, band:uint = ALL_BANDS):Number
		{
			var avg:Number = 0;
			var bandMin:uint;
			var bandMax:uint;
			
			// Get the correct bounds for the selected band
			switch(band)
			{
				case LOW_BAND: bandMin = LOW_BAND_MIN; bandMax = LOW_BAND_MAX; break;
				case MID_BAND: bandMin = MID_BAND_MIN; bandMax = MID_BAND_MAX; break;
				case HIGH_BAND: bandMin = HIGH_BAND_MIN; bandMax = HIGH_BAND_MAX; break;
				default: bandMin = ALL_BANDS_MIN; bandMax = ALL_BANDS_MAX; break;
			}
			
			// Find Recent Volume In Band Range
			var i:uint;
			var j:uint;
			for(i=0;i<frames;i++)
			{
				for(j=bandMin;j<=bandMax;j++)
				{
					avg += ampHistory[(ampIndex+(FRAME_RATE-i))%FRAME_RATE][j];
				}
			}
			return avg/(frames*(bandMax+1-bandMin));
		}
		
		protected function historicalVolAvg(band:uint = ALL_BANDS):Number
		{
			// Variables
			var avg:Number = 0;
			var bandMin:uint;
			var bandMax:uint;
			
			// Get the correct bounds for the selected band
			switch(band)
			{
				case LOW_BAND: bandMin = LOW_BAND_MIN; bandMax = LOW_BAND_MAX; break;
				case MID_BAND: bandMin = MID_BAND_MIN; bandMax = MID_BAND_MAX; break;
				case HIGH_BAND: bandMin = HIGH_BAND_MIN; bandMax = HIGH_BAND_MAX; break;
				default: bandMin = ALL_BANDS_MIN; bandMax = ALL_BANDS_MAX; break;
			}
			
			// Find Historical Volume In Band Range
			var i:uint;
			var j:uint;
			for(i=0;i<FRAME_RATE;i++)
			{
				for(j=bandMin;j<=bandMax;j++)
				{
					avg += ampHistory[i][j];
				}
			}			
			return avg/(FRAME_RATE*(bandMax+1-bandMin));
		}
		
		protected function avgVol():Number
		{
			var vol:Number = 0;
			
			var i:uint;
			for(i=0;i<amplitudes.length;i++)
			{
				if(amplitudes[i] < 0) vol += 0;
				else vol += amplitudes[i];
			}
			
			return vol/amplitudes.length;
		}
		
		
		//---------- PRIVATE FUNCTIONS ----------//
		private function onEnterFrame(event:Event):void
		{
			// Calculate a logrithmic spectrum of the sound data (10 bands at 1/3 octave bandwidth)
			amplitudes = FrequencyAnalyzer.computeFrequencies(FrequencyAnalyzer.TEN_BAND, 1/3);
			
			// Update Historical Amplitude Data
			updateHistory();
			
			// Analyze data and dispatch beat events
			dispatchEvents();
			
			// Draw the frame
			drawFrame();
		}
		
		private function dispatchEvents()
		{
			// Dispatch Low Band Beat
			if(isLowBandBeat()) dispatchEvent(new BeatEvent(BeatEvent.LOW_BAND_BEAT));
			
			// Dispatch Mid Band Beat
			if(isMidBandBeat()) dispatchEvent(new BeatEvent(BeatEvent.MID_BAND_BEAT));
			
			// Dispatch High Band Beat
			if(isHighBandBeat()) dispatchEvent(new BeatEvent(BeatEvent.HIGH_BAND_BEAT));
		}
		
		private function updateHistory()
		{
			// Update ampHistory with the current amplitude info
			ampHistory[ampIndex] = amplitudes;
			ampIndex = (++ampIndex)%FRAME_RATE;
		}
		
		private function isLowBandBeat(): Boolean
		{
			// A beat has occurred if the recent volume exceeds the historical volume
			if(recentVolAvg(3,LOW_BAND)/historicalVolAvg(LOW_BAND) >= BEAT_THRESHOLD)
			{
				// Beat occured, only return true if it hasn't previously been registered
				if(!lowBeat) return lowBeat=true;
			}
			else
			{
				// No beat, unregister previous beats
				return lowBeat = false;
			}
			
			// Not Reached
			return false;
		}
		
		private function isMidBandBeat(): Boolean
		{
			// A beat has occurred if the recent volume exceeds the historical volume
			if(recentVolAvg(3,MID_BAND)/historicalVolAvg(MID_BAND) >= BEAT_THRESHOLD)
			{
				// Beat occured, only return true if it hasn't previously been registered
				if(!midBeat) return midBeat=true;
			}
			else
			{
				// No beat, unregister previous beats
				return midBeat = false;
			}
			
			// Not Reached
			return false;
		}
		
		private function isHighBandBeat(): Boolean
		{
			// A beat has occurred if the recent volume exceeds the historical volume
			if(recentVolAvg(3,HIGH_BAND)/historicalVolAvg(HIGH_BAND) >= BEAT_THRESHOLD)
			{
				// Beat occured, only return true if it hasn't previously been registered
				if(!highBeat) return highBeat=true;
			}
			else
			{
				// No beat, unregister previous beats
				return highBeat = false;
			}
			
			// Not Reached
			return false;
		}
	}	
}